Skip to content

feat: if condition exists, updating it, otherwise inserting it to the conditions list in the CRD, in the reconcile function#260

Open
SpaceFace02 wants to merge 4 commits into
trusted-execution-clusters:mainfrom
SpaceFace02:bugfix/upsert-existing-conditions-no-overwrite
Open

feat: if condition exists, updating it, otherwise inserting it to the conditions list in the CRD, in the reconcile function#260
SpaceFace02 wants to merge 4 commits into
trusted-execution-clusters:mainfrom
SpaceFace02:bugfix/upsert-existing-conditions-no-overwrite

Conversation

@SpaceFace02
Copy link
Copy Markdown
Contributor

@SpaceFace02 SpaceFace02 commented May 20, 2026

Bug fix for: #220

Instead of recreating the conditions from scratch each time the reconcile function runs, this makes sure that foreign controller conditions are not overwritten, and same type conditions are updated, rather then recreated; new conditions are inserted.

Summary by Sourcery

Preserve and incrementally update status conditions during reconcile instead of rebuilding them from scratch, while tightening related tests and minor code ergonomics.

Bug Fixes:

  • Ensure reconcile upserts status conditions so operator-owned conditions are updated or inserted without overwriting conditions from other controllers.
  • Fix PCR computation handling by correctly tracking when PCRs should be recomputed for new images.

Enhancements:

  • Introduce a shared upsert_condition helper to update or append conditions by type.
  • Refine TLS startup logic for services to use safer option pattern matching.
  • Rename the register server service port to a more specific name to avoid ambiguity in service configuration.
  • Improve test helpers to assert multiple expected substrings in PATCH bodies with clearer error messages.
  • Clarify documentation for running individual integration tests and required environment setup.

Tests:

  • Extend reconcile tests to verify that foreign controller conditions are preserved while operator conditions are inserted or updated, and that only a single Installed condition is present and transitioned to True.
  • Update existing reconcile tests to use the new multi-string assertion helper for PATCH request validation.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 20, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: SpaceFace02

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 20, 2026

Reviewer's Guide

This PR changes the reconcile flow to preserve existing status conditions by introducing a generic upsert helper, updates reconcile/tests to assert that foreign controller conditions are preserved and operator-owned conditions are updated in place, and applies several small readability/robustness cleanups across the codebase and test docs.

Sequence diagram for reconcile status condition upsert flow

sequenceDiagram
    participant Reconcile as reconcile
    participant Status as TrustedExecutionClusterStatus
    participant Upsert as upsert_condition
    participant Kube as update_status

    Reconcile->>Status: read status.conditions
    Status-->>Reconcile: Option<Vec<Condition>>

    Reconcile->>Upsert: upsert_condition(conditions, address_condition)
    Reconcile->>Upsert: upsert_condition(conditions, uninstall_condition)
    Reconcile->>Upsert: upsert_condition(conditions, non_unique_condition)
    Reconcile->>Upsert: upsert_condition(conditions, installing_condition)
    Reconcile->>Upsert: upsert_condition(conditions, installed_condition)

    Upsert-->>Reconcile: conditions updated in place

    Reconcile->>Kube: update_status(TrustedExecutionClusterStatus { conditions })
    Kube-->>Reconcile: Action::await_change / Action::requeue
Loading

File-Level Changes

Change Details Files
Reconcile no longer recreates the status.conditions list, but instead upserts operator-owned conditions while preserving foreign ones.
  • Stop initializing conditions with a fresh vec, instead start from existing status.conditions when present.
  • Use the new upsert_condition helper for all Installed/known trustee address condition updates, including uninstall, non-unique, installing, and installed paths.
  • Ensure status updates reuse and clone the evolving conditions vector instead of rebuilding temporary copies like installing.
operator/src/main.rs
Introduce a reusable upsert_condition helper to update or insert status conditions by type.
  • Add upsert_condition that lazily initializes the Option<Vec> and replaces an existing condition with the same type_ or pushes a new one.
  • Expose upsert_condition from the trusted_cluster_operator_lib so it can be used from the operator reconcile logic.
lib/src/lib.rs
Strengthen reconcile-related tests to validate condition preservation/updating and generalize request body assertions.
  • Extend assert_body_contains to accept multiple expected substrings with optional custom error messages.
  • Update existing uninstall and non-unique reconcile tests to use the new helper and clearer error messaging.
  • Add new tests that verify uninstall preserves foreign conditions and that install preserves foreign conditions while updating the Installed condition in place with exactly one entry set to True.
operator/src/main.rs
test_utils/src/mock_client.rs
Improve readability and intent in ancillary components (reference values, TLS setup, service port naming, and test docs).
  • Rename compute_pcrs flag to should_compute_pcrs and update related comment to better reflect intent.
  • Refactor TLS selection logic in binaries to use if let on the cert/key pair for safer/clearer handling.
  • Rename the register-server service port from "http" to "register-server-port" for disambiguation between HTTP/HTTPS.
  • Document environment variables and manifest generation required before running individual integration tests.
operator/src/reference_values.rs
register-server/src/main.rs
attestation-key-register/src/main.rs
operator/src/register_server.rs
tests/README.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The upsert_condition helper currently identifies conditions only by type_; if there’s any chance multiple controllers share a condition type, you may want to refine the match (e.g., include controller-specific markers or reason) to avoid unintentionally overwriting another controller’s condition of the same type.
  • In reconcile, conditions is cloned solely to build the intermediate installing status; you could avoid that extra allocation by updating in-place and reusing the same conditions value for both the installing and installed status updates.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `upsert_condition` helper currently identifies conditions only by `type_`; if there’s any chance multiple controllers share a condition type, you may want to refine the match (e.g., include controller-specific markers or reason) to avoid unintentionally overwriting another controller’s condition of the same type.
- In `reconcile`, `conditions` is cloned solely to build the intermediate `installing` status; you could avoid that extra allocation by updating in-place and reusing the same `conditions` value for both the installing and installed status updates.

## Individual Comments

### Comment 1
<location path="lib/src/lib.rs" line_range="158-155" />
<code_context>
 }
+
+// Update condition if already present, otherwise append(insert) it into the conditions vector.
+pub fn upsert_condition(conditions: &mut Option<Vec<Condition>>, condition: Condition) {
+    let conditions_vec = conditions.get_or_insert_with(Vec::new);
+    if let Some(existing) = conditions_vec.iter_mut().find(|c| c.type_ == condition.type_){
+        *existing = condition;
+    } else {
+        conditions_vec.push(condition);
+    }
+}
\ No newline at end of file
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid overwriting identical conditions to preserve transition semantics and reduce unnecessary status churn.

Because `upsert_condition` always replaces the existing `Condition` when `type_` matches, it resets `last_transition_time` and other fields even when nothing has actually changed. This creates noisy status updates and obscures real transitions. Consider short-circuiting when the incoming condition is effectively the same (e.g., `status`, `reason`, and `message` unchanged), and only updating when there is a meaningful change to preserve transition semantics and avoid unnecessary PATCHes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lib/src/lib.rs
@SpaceFace02 SpaceFace02 marked this pull request as draft May 20, 2026 04:23
@SpaceFace02 SpaceFace02 force-pushed the bugfix/upsert-existing-conditions-no-overwrite branch from e883858 to 66267de Compare May 20, 2026 04:38
@SpaceFace02
Copy link
Copy Markdown
Contributor Author

I believe we have a setting which makes cargo fmt and clippy return warnings as errors, which made me change a few other parts of the code I did not modify for the feature. Is this intentional?

@SpaceFace02 SpaceFace02 self-assigned this May 20, 2026
@SpaceFace02 SpaceFace02 marked this pull request as ready for review May 20, 2026 05:23
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@alicefr
Copy link
Copy Markdown
Contributor

alicefr commented May 20, 2026

I believe we have a setting which makes cargo fmt and clippy return warnings as errors, which made me change a few other parts of the code I did not modify for the feature. Is this intentional?

Weird, we have this job that check the cargo and clippy. Might be that the version you have installed is more recent than the toolchain in the CI

EDIT: from the CI the version is toolchain install 1.88.0

Comment thread operator/src/reference_values.rs
Comment thread operator/src/register_server.rs
Comment thread tests/README.md Outdated
@alicefr
Copy link
Copy Markdown
Contributor

alicefr commented May 20, 2026

@SpaceFace02 thanks, just a couple of nits

@SpaceFace02 SpaceFace02 force-pushed the bugfix/upsert-existing-conditions-no-overwrite branch from 78ed44f to 105a3ca Compare May 20, 2026 10:27
@Jakob-Naucke Jakob-Naucke linked an issue May 21, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@Jakob-Naucke Jakob-Naucke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • upsert_condition should also be used by image_add_reconcile
  • Please put fixes by rustfmt in the same commit

Tip: If you put Fixes: #220 in a commit message, it will auto-link that issue. I've linked it for now.

let run = if args.cert_path.is_some() && args.key_path.is_some() {
let config = OpenSSLConfig::from_pem_file(args.cert_path.unwrap(), args.key_path.unwrap())
.expect("invalid PEM files");
let run = if let (Some(cert_path), Some(key_path)) = (args.cert_path, args.key_path) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also in response to

I believe we have a setting which makes cargo fmt and clippy return warnings as errors, which made me change a few other parts of the code I did not modify for the feature. Is this intentional?

Weird, we have this job that check the cargo and clippy. Might be that the version you have installed is more recent than the toolchain in the CI

)

I agree with this change, and you're invited to keep making them (in separate commits like you did here), but let me give some context as to why it's not caught by CI in the first place: This has only become a lint recently, and we use a Rust version supported by RHEL/UBI to avoid downstream patching when using a Rust feature that's newer than that. We also use the linting as of that version because sometimes a new Rust feature also becomes a lint to use that feature relatively fast or even in the same version.

With that said, we could update to 1.92 (not for this PR). Ideally it's even possible to get dependabot to update to a version supported by e.g. registry.access.redhat.com/ubi9. This could maybe also go in a doc then.

Comment thread tests/README.md Outdated
Comment on lines +25 to +27
Each test can also be run independently using cargo test. Example:

Note: before running independent tests, make sure TRUSTEE_IMAGE, APPROVED_IMAGE, TEST_IMAGE, RUST_LOG, REGISTRY, TAG env variables are set. Also run `make trusted-cluster-gen` to create the minimal/default boiler-plate yaml manifests for the TEC.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good addition, but nit for flow & formatting

Suggested change
Each test can also be run independently using cargo test. Example:
Note: before running independent tests, make sure TRUSTEE_IMAGE, APPROVED_IMAGE, TEST_IMAGE, RUST_LOG, REGISTRY, TAG env variables are set. Also run `make trusted-cluster-gen` to create the minimal/default boiler-plate yaml manifests for the TEC.
Each test can also be run independently using cargo test.
Before running independent tests, run `make trusted-cluster-gen` to create the default boiler-plate yaml manifests for the TEC and make sure `TRUSTEE_IMAGE`, `APPROVED_IMAGE`, `TEST_IMAGE`, `RUST_LOG`, `REGISTRY`, `TAG` env variables are set.
Example:

Comment thread operator/src/register_server.rs Outdated
selector: Some(labels),
ports: Some(vec![ServicePort {
name: Some("http".to_string()),
// Renamed to register-server-port for disambiguation from http, as the presence of a secret indicates http or https.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good change, but this comment should be the commit message -- no need to have the code history in the code itself

Comment thread lib/src/lib.rs Outdated
}

// Update condition if already present, otherwise append(insert) it into the conditions vector.
pub fn upsert_condition(conditions: &mut Option<Vec<Condition>>, condition: Condition) -> bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a bit of a rewrite of https://github.com/kubernetes/apimachinery/blob/a7225b106d19985c2a47654c106cb419007af6d2/pkg/api/meta/conditions.go#L31. Not for this PR, but you could add a TODO to contribute that functionality to kube-rs.

On a different note, this should go to operator/src/lib.rs because you're only using it in-operator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great find, yes I'll add the TODO to port it to kube-rs in the future.

And yes, I'll move it to operator/src/lib.rs

let body = String::from_utf8_lossy(&bytes);
assert!(body.contains(contains));

for (expected_reason, expected_err_msg) in expected {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the loop refactor is a little overkill for one use

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I didn't want to do it initially, but in order to test multiple conditions and to see whether all fields are preserved, I added this.

this method needs ownership of body, as it consumes it, and I didn't want to clone too many times in the caller function, so I thought this is a better way to handle this, albeit a bit verbose.

LMK

Comment thread operator/src/main.rs Outdated
_ => panic!("unexpected: {req:?}"),
};

{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check, and the one below, don't need to be in blocks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had added blocks because there was some import scoping issue of atomic crate, it was being imported in the macro, and since it wasn't scoped initially, it was throwing a double import issue, have fixed it.

Comment thread operator/src/main.rs Outdated
#[tokio::test]
async fn test_reconcile_uninstall_preserves_foreign_controller_condition_by_inserting_owned_condition()
{
let foreign_condition = Condition {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I do think this Condition could be generated from a dummy function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch, I am using it in 2 places with exact initialization

Comment thread operator/src/main.rs Outdated

// Makes sure that uninstall trigger preserves foreign independent controller conditions, and our operator doesn't overwrite it in the reconcile function. Tests insert of our upsert_condition function.
#[tokio::test]
async fn test_reconcile_uninstall_preserves_foreign_controller_condition_by_inserting_owned_condition()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which owned condition? Maybe just test_reconcile_uninstall_preserves_foreign_condition. Same for test_reconcile_install_preserves_foreign_condition_while_updating_owned_condition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as these tests belong to operator, this is related to all conditions owned by operator for managing the TEC CRD. In the future, we is possible that we may have certain fields of TEC not owned by operator, and owned by some foreign controller

Comment thread operator/src/main.rs
"Request body did not contain ForeignCondition, got: {body}"
);

if body.contains(INSTALLED_REASON) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in a block, don't we always expect that there is exactly one Installed condition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is only 1 installed condition, but 2 patch calls with different reasons (one with Installing reason and one for InstallationCompleted reason).

Here I am checking that in the last PATCH call (with body containing Reason=InstallationCompleted), does reconcile update existing Installed Condition with new status and reason (expected behaviour), or does it append another condition instead (what we don't want).

@SpaceFace02 SpaceFace02 force-pushed the bugfix/upsert-existing-conditions-no-overwrite branch from f1f4e7f to 41e43e4 Compare May 25, 2026 07:02
@SpaceFace02
Copy link
Copy Markdown
Contributor Author

upsert_condition should also be used by image_add_reconcile

Have added the upsert_condition for both image_add_reconcile and ak_reconcile.

… conditions list in the CRD, in the main reconcile function of operator, attestation key register ak reconcile and reference image_add_reconcile

Fix: trusted-execution-clusters#220
…presence of a secret indicates http or https.
@SpaceFace02 SpaceFace02 force-pushed the bugfix/upsert-existing-conditions-no-overwrite branch from 41e43e4 to 824d2ee Compare May 25, 2026 07:19
@SpaceFace02
Copy link
Copy Markdown
Contributor Author

Tip: If you put Fixes: #220 in a commit message, it will auto-link that issue. I've linked it for now.

By adding this to commit message, it will reference the commit instead of the PR right, so once that commit checks in the main branch, will it close the PR and resolve the issue?

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Conditions are always overwritten on the update

3 participants